package org.kingtop.mybatis.mapper;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.scripting.defaults.RawSqlSource;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.kingtop.mybatis.kengen.KeyGeneratorEnum;
import org.kingtop.mybatis.kengen.KeyGeneratorFactory;
import org.kingtop.mybatis.persistence.metamodel.DbTypeEnum;
import org.kingtop.mybatis.persistence.metamodel.TableField;
import org.kingtop.mybatis.persistence.metamodel.TableInfo;
import org.kingtop.mybatis.util.JdbcConstants;
import org.kingtop.mybatis.util.JdbcUtils;
import org.kingtop.mybatis.util.ReflectUtil;
import org.kingtop.mybatis.util.TableInfoUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * mybatis默认的sql生成
 *
 * @author 陈金波
 * @date 2018年1月7日 下午10:55:30
 * @Copyright: 2018 chen jinbo. All rights reserved.
 */
public class SqlInjector {

    protected Logger logger = LoggerFactory.getLogger(getClass());
    /**
     * _ID_SEQU，oracle参数辅助
     */
    public static final String KEY_INCREAMENT_ORACLE = "_ID_SEQU";
    /**
     * ,jdbcType=
     */
    private static final String SQL_JDBCTYPE_ARG = ",jdbcType=";
    /**
     * 循环遍历id
     */
    private static final String SQL_FOREACH_ITEM = "\n<foreach item=\"item\" index=\"index\" collection=\"list\" open=\"(\" separator=\",\" close=\")\">#{item}\n</foreach>";
    /**
     * 逗号间隔符
     */
    private static final String SQL_SPACER_COMMA = ",";
    /**
     * 更新SQL的字段
     */
    private static final String SQL_UPDATE_FIELD = "%s=#{%s" + SQL_JDBCTYPE_ARG + "%s},";
    /**
     * 排序语句
     */
    private static final String SQL_SELECT_ORDER = " ORDER BY %s";

    /**
     * 更新SQL的字段,增加非空判断
     */
    private static final String SQL_UPDATE_FIELD_NOT_NULL = "<if test=\"%s!=null\">" + SQL_UPDATE_FIELD + "</if>";
    /**
     * 判断值是否为Null
     */
    private static final String TEST_FOR_NOT_NULL = "<if test=\"%s!=null\"> and %s=#{%s}</if>";
    /**
     * 判断值是否为null且不为空字符串
     */
    private static final String TEST_FOR_NOT_BLANK = "<if test=\"%s!=null and %s!='' \"> and %s=#{%s}</if>";

    /**
     * 参数为list的数据类型
     */
    private static final Class<?> LIST_CLASS = new ArrayList<Long>().getClass();

    private final MapperBuilderAssistant builderAssistant;

    private final Configuration configuration;
    protected LanguageDriver languageDriver;

    /**
     * 数据库类型
     */
    private String dbType;

    /**
     * dao接口
     */
    private final Class<?> type;

    /**
     * PO类
     */
    private Class<?> modelClass;

    /**
     * modelClass的相关信息
     */
    private TableInfo tableInfo;

    /**
     * 构造函数
     *
     * @param configuration 配置信息
     * @param type          要生成方法的dao接口
     */
    public SqlInjector(Configuration configuration, Class<?> type) {
        this(configuration, type, null);
    }

    /**
     * 构造函数
     *
     * @param configuration 配置信息
     * @param type          要生成方法的dao接口
     * @param dbType        数据库类型
     */
    public SqlInjector(Configuration configuration, Class<?> type, String dbType) {
        super();
        String resource = type.getName().replace('.', '/') + ".java (best guess)";
        this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
        this.configuration = configuration;
        this.type = type;
        this.dbType = dbType;
    }

    /**
     * 初始化部分参数
     *
     * @return void
     * @author 陈金波
     * @date 2018年1月9日 下午7:36:09
     */
    public void init() {
        this.builderAssistant.setCurrentNamespace(type.getName());
        if (this.dbType == null) {
            try {
                Connection connection = this.configuration.getEnvironment().getDataSource().getConnection();
                this.dbType = JdbcUtils.getDbType(connection.getMetaData().getURL());
            } catch (SQLException e) {
                e.printStackTrace();
                logger.error("dbtype missing", e);
            }
        }
        this.modelClass = ReflectUtil.extractModelClass(type);
        tableInfo = TableInfoUtil.getTableInfo(modelClass);
        if (this.languageDriver == null) {
            this.languageDriver = configuration.getDefaultScriptingLanguageInstance();
        }
    }

    /**
     * 插入所有的SQL语句
     *
     * @return void
     * @author 陈金波
     * @date 2018年1月9日 下午7:38:01
     */
    public void injectAllSql() {
        injectInsertSql();
        injectGetByIdSql();
        injectListByIdsSql();
        injectListByBean();
        injectListAllSql();
        injectPageByBean();
        injectDeleteSql();
        injectDeleteByIdsSql();
        injectDeleteByBeanSql();
        injectUpdateSql();
        injectUpdateForNotNullSql();
    }

    /**
     * 根据java类中的字段信息，获取与之匹配的jdbcType
     *
     * @param tableField java字段信息
     * @return jdbc类型
     */
    protected String getJdbcType(TableField tableField) {
        if (tableField.getJdbcType() != null && tableField.getJdbcType() != JdbcType.OTHER) {
            return tableField.getJdbcType().toString();
        } else {
            DbTypeEnum dbTypeEnum = DbTypeEnum.forJavaType(dbType, tableField.getJavaType());
            if (dbTypeEnum != null) {
                return dbTypeEnum.getJdbcType().toString();
            } else {
                return JdbcType.OTHER.toString();
            }
        }
    }

    /**
     * 注入插入 SQL 语句
     */
    public MappedStatement injectInsertSql() {
        // 设置主键生成方式
        KeyGenerator keyGenerator = KeyGeneratorFactory.getKeyGeneratorByKey(tableInfo);

        StringBuilder fieldBuilder = new StringBuilder();
        StringBuilder placeholderBuilder = new StringBuilder();
        // 判断是否需要设置主键
        String keyProperty = null;
        String keyColumn = null;
        if (tableInfo.getPkField() != null) {
            keyProperty = tableInfo.getPkField().getJavaFieldName();
            keyColumn = tableInfo.getPkField().getDbFieldName();
            if (tableInfo.getPkKey() == KeyGeneratorEnum.JDBC3_KEY) {
                TableField pkField = tableInfo.getPkField();
                if (JdbcConstants.ORACLE.equalsIgnoreCase(dbType)) {
                    // 如果是oracle使用sequence进行自增
                    fieldBuilder.append(pkField.getDbFieldName()).append(SQL_SPACER_COMMA);
                    int startIndex = tableInfo.getTableNamePrefix().length();
                    int endIndex = tableInfo.getTableName().length();
                    String sequenceName = tableInfo.getTableName().substring(startIndex, endIndex)
                            + KEY_INCREAMENT_ORACLE;
                    if (pkField.getParams() != null
                            && StringUtils.isNotBlank(pkField.getParams().get(TableField.SEQUENCE_PARAM_NAME))) {
                        sequenceName = pkField.getParams().get(TableField.SEQUENCE_PARAM_NAME);
                    }
                    placeholderBuilder.append(sequenceName).append(".NEXTVAL" + SQL_SPACER_COMMA);
                } else {// dbType == null || dbType.isEmpty() ||
                    // !JdbcConstants.ORACLE.equals(dbType)
                    fieldBuilder.append(pkField.getDbFieldName()).append(SQL_SPACER_COMMA);
                    placeholderBuilder.append("#{").append(pkField.getJavaFieldName()).append("}" + SQL_SPACER_COMMA);
                }
            } else {
                fieldBuilder.append(tableInfo.getPkField().getDbFieldName()).append(SQL_SPACER_COMMA);
                placeholderBuilder.append("#{").append(tableInfo.getPkField().getJavaFieldName()).append("}" + SQL_SPACER_COMMA);
            }
        }
        // 构建字段
        List<TableField> fieldList = tableInfo.getFieldList();
        for (TableField tableField : fieldList) {
            fieldBuilder.append(tableField.getDbFieldName()).append(SQL_SPACER_COMMA);
            placeholderBuilder.append("#{").append(tableField.getJavaFieldName()).append(SQL_JDBCTYPE_ARG).append(getJdbcType(tableField)).append("}" + SQL_SPACER_COMMA);
        }
        // 截断逗号
        String fieldSql = fieldBuilder.toString();
        if (fieldSql.endsWith(SQL_SPACER_COMMA)) {
            fieldSql = fieldSql.substring(0, fieldSql.length() - 1);
        }
        String placeholderSql = placeholderBuilder.toString();
        if (placeholderSql.endsWith(SQL_SPACER_COMMA)) {
            placeholderSql = placeholderSql.substring(0, placeholderSql.length() - 1);
        }
        // 创建SQL语句
        String sql = String.format(SqlMethodEnum.INSERT.getSql(), tableInfo.getTableName(), fieldSql, placeholderSql);
        SqlSource sqlSource = new RawSqlSource(configuration, sql, modelClass);
        return this.addMappedStatement(SqlMethodEnum.INSERT.getMethodId(), sqlSource, SqlCommandType.INSERT, modelClass, Long.class,
                keyGenerator, keyProperty, keyColumn);
    }

    /**
     * 注入更新的sql语句
     *
     * @author 陈金波
     * @date 2018年1月4日 上午9:30:21
     */
    public MappedStatement injectUpdateSql() {
        // 构建字段
        String fieldSql = getUpdateSql(false);
        // 条件语句
        String whereSql = tableInfo.getPkField().getDbFieldName() + "=#{" + tableInfo.getPkField().getJavaFieldName() + "}";

        String sql = String.format(SqlMethodEnum.UPDATE_BY_PK.getSql(), tableInfo.getTableName(), fieldSql, whereSql);
        SqlSource sqlSource = new RawSqlSource(configuration, sql, modelClass);
        return this.addMappedStatement(SqlMethodEnum.UPDATE_BY_PK.getMethodId(), sqlSource, SqlCommandType.UPDATE, modelClass, null,
                new NoKeyGenerator(), null, null);
    }

    /**
     * 注入更新的sql语句,判断数值是否为null
     *
     * @author 陈金波
     * @date 2018年2月4日 下午9:30:21
     */
    public MappedStatement injectUpdateForNotNullSql() {
        // 构建字段
        String fieldSql = getUpdateSql(true);
        // 条件语句
        String whereSql = tableInfo.getPkField().getDbFieldName() + "=#{" + tableInfo.getPkField().getJavaFieldName() + "}";

        String sql = String.format(SqlMethodEnum.UPDATE_FOR_NOT_NULL_BY_PK.getSql(), tableInfo.getTableName(), fieldSql, whereSql);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addMappedStatement(SqlMethodEnum.UPDATE_FOR_NOT_NULL_BY_PK.getMethodId(), sqlSource, SqlCommandType.UPDATE, modelClass, null,
                new NoKeyGenerator(), null, null);
    }

    /**
     * 注入删除语句
     *
     * @author 陈金波
     * @date 2018年1月4日 上午9:35:24
     */
    public MappedStatement injectDeleteSql() {
        String sql = String.format(SqlMethodEnum.DELETE_BY_PK.getSql(), tableInfo.getTableName(), tableInfo.getPkField().getDbFieldName(),
                tableInfo.getPkField().getJavaFieldName());
        SqlSource sqlSource = new RawSqlSource(configuration, sql, modelClass);
        return addDeleteMappedStatement(SqlMethodEnum.DELETE_BY_PK.getMethodId(), sqlSource, tableInfo.getPkField().getJavaType());
    }

    /**
     * 插入通过多个主键删除的语句
     */
    public MappedStatement injectDeleteByIdsSql() {
        String sql = String.format(SqlMethodEnum.DELETE_BY_PKS.getSql(), tableInfo.getTableName(), tableInfo.getPkField().getDbFieldName(), SQL_FOREACH_ITEM);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addDeleteMappedStatement(SqlMethodEnum.DELETE_BY_PKS.getMethodId(), sqlSource, LIST_CLASS);
    }

    /**
     * 插入通过条件删除的语句
     */
    public MappedStatement injectDeleteByBeanSql() {
        String whereSql = getWhereSql();
        String sql = String.format(SqlMethodEnum.DELETE_BY_BEAN.getSql(), tableInfo.getTableName(), whereSql);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addDeleteMappedStatement(SqlMethodEnum.DELETE_BY_BEAN.getMethodId(), sqlSource, modelClass);
    }

    /**
     * 删除
     */
    public MappedStatement addDeleteMappedStatement(String id, SqlSource sqlSource, Class<?> parameterType) {
        return this.addMappedStatement(id, sqlSource, SqlCommandType.DELETE, parameterType, Integer.class,
                new NoKeyGenerator(), null, null);
    }

    /**
     * 注入通过主键查询信息的sql语句
     *
     * @return MappedStatement
     * @author 陈金波
     * @date 2018年1月4日 上午9:35:48
     */
    public MappedStatement injectGetByIdSql() {
        String selectSql = getSelectSql();

        String sql = String.format(SqlMethodEnum.SELECT_ONE_BY_PK.getSql(), selectSql, tableInfo.getTableName(),
                tableInfo.getPkField().getDbFieldName(), tableInfo.getPkField().getJavaFieldName());
        SqlSource sqlSource = new RawSqlSource(configuration, sql, modelClass);
        return this.addSelectMappedStatement(SqlMethodEnum.SELECT_ONE_BY_PK.getMethodId(), sqlSource, tableInfo.getPkField().getJavaType(), modelClass);
    }

    /**
     * 构建通过多个主键查询数据的SQL语句
     *
     * @return MappedStatement
     */
    public MappedStatement injectListByIdsSql() {
        String selectSql = getSelectSql();
        String sql = String.format(SqlMethodEnum.SELECT_LIST_BY_PKS.getSql(), selectSql, tableInfo.getTableName(),
                tableInfo.getPkField().getDbFieldName(), SQL_FOREACH_ITEM);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addSelectMappedStatement(SqlMethodEnum.SELECT_LIST_BY_PKS.getMethodId(), sqlSource, LIST_CLASS, modelClass);
    }

    /**
     * 插入通过条件查询的SQL语句
     *
     * @return MappedStatement
     */
    public MappedStatement injectListByBean() {
        String selectSql = getSelectSql();
        String whereSql = getWhereSql();
        String sql = String.format(SqlMethodEnum.SELECT_LIST_BY_BEAN.getSql(), selectSql, tableInfo.getTableName(), whereSql, getOrderSql());
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addSelectMappedStatement(SqlMethodEnum.SELECT_LIST_BY_BEAN.getMethodId(), sqlSource, modelClass, modelClass);
    }

    /**
     * 注入查询所有的sql语句
     *
     * @return void
     * @author 陈金波
     * @date 2018年1月4日 上午9:36:14
     */
    public MappedStatement injectListAllSql() {
        String selectSql = getSelectSql();

        String sql = String.format(SqlMethodEnum.SELECT_LIST_ALL.getSql(), selectSql, tableInfo.getTableName());
        SqlSource sqlSource = new RawSqlSource(configuration, sql, modelClass);
        return this.addSelectMappedStatement(SqlMethodEnum.SELECT_LIST_ALL.getMethodId(), sqlSource, null, modelClass);
    }

    /**
     * 插入通过条件查询的分页SQL语句
     *
     * @return MappedStatement
     */
    public MappedStatement injectPageByBean() {
        String selectSql = getSelectSql();
        String whereSql = getWhereSql();
        String sql = String.format(SqlMethodEnum.SELECT_PAGE_BY_BEAN.getSql(), selectSql, tableInfo.getTableName(), whereSql, getOrderSql());
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addSelectMappedStatement(SqlMethodEnum.SELECT_PAGE_BY_BEAN.getMethodId(), sqlSource, modelClass, modelClass);
    }

    /**
     * 构建查询语句的select部分
     */
    private String getSelectSql() {
        StringBuilder selectBuilder = new StringBuilder();
        if (tableInfo.getPkField() != null) {
            selectBuilder.append(tableInfo.getPkField().getDbFieldName()).append(SQL_SPACER_COMMA);
        }
        // 构建字段
        List<TableField> fieldList = tableInfo.getFieldList();
        for (TableField tableField : fieldList) {
            selectBuilder.append(tableField.getDbFieldName()).append(SQL_SPACER_COMMA);
        }
        String selectSql = selectBuilder.toString();
        if (selectSql.endsWith(SQL_SPACER_COMMA)) {
            selectSql = selectSql.substring(0, selectSql.length() - 1);
        }
        return selectSql;
    }

    /**
     * 构建查询语句，加了是否为null的判断
     *
     * @return 查询的SQL语句
     */
    private String getWhereSql() {
        StringBuilder sqlBuilder = new StringBuilder("<where>");
        if (tableInfo.getPkField() != null) {
            //"<if test=\"%s!=null\"> and %s=#{%s}</if>";
            sqlBuilder.append(String.format(TEST_FOR_NOT_NULL, tableInfo.getPkField().getJavaFieldName(), tableInfo.getPkField().getDbFieldName(), tableInfo.getPkField().getJavaFieldName()));
        }
        // 构建字段
        List<TableField> fieldList = tableInfo.getFieldList();
        for (TableField tableField : fieldList) {
            if (String.class == tableField.getJavaType()) {
                sqlBuilder.append(String.format(TEST_FOR_NOT_BLANK, tableField.getJavaFieldName(), tableField.getJavaFieldName(),
                        tableField.getDbFieldName(), tableField.getJavaFieldName()));
            } else {
                sqlBuilder.append(String.format(TEST_FOR_NOT_NULL, tableField.getJavaFieldName(), tableField.getDbFieldName(), tableField.getJavaFieldName()));
            }
        }
        sqlBuilder.append("</where>");
        return sqlBuilder.toString();
    }

    /**
     * 获取更新的SQL语句
     *
     * @param includeTest 是否做null的判断
     * @return SQL语句
     */
    private String getUpdateSql(boolean includeTest) {
        StringBuilder sqlBuilder = new StringBuilder();
        List<TableField> fieldList = tableInfo.getFieldList();
        for (TableField tableField : fieldList) {
            //判断是否增加null的判断
            if (!includeTest) {
                sqlBuilder.append(String.format(SQL_UPDATE_FIELD, tableField.getDbFieldName(), tableField.getJavaFieldName(), getJdbcType(tableField)));
            } else {
                sqlBuilder.append(String.format(SQL_UPDATE_FIELD_NOT_NULL, tableField.getJavaFieldName(), tableField.getDbFieldName(), tableField.getJavaFieldName(), getJdbcType(tableField)));
            }
        }
        String fieldSql = sqlBuilder.toString();
        if (fieldSql.endsWith(SQL_SPACER_COMMA)) {
            fieldSql = fieldSql.substring(0, fieldSql.length() - 1);
        }
        return fieldSql;
    }

    /**
     * 获取排序语句
     * @return
     */
    private String getOrderSql(){
        StringBuilder sqlBuilder = new StringBuilder();
        if(tableInfo.getPkField().isOrder()){
            sqlBuilder.append(tableInfo.getPkField().getDbFieldName() + SQL_SPACER_COMMA);
        }
        for (TableField tableField : tableInfo.getFieldList()) {
            if(tableField.isOrder()){
                sqlBuilder.append(tableField.getDbFieldName() + SQL_SPACER_COMMA);
            }
        }
        String orderSql = sqlBuilder.toString();
        if (orderSql.endsWith(SQL_SPACER_COMMA)) {
            orderSql = orderSql.substring(0, orderSql.length() - 1);
        }
        if(StringUtils.isNotBlank(orderSql)) {
            return String.format(SQL_SELECT_ORDER, orderSql);
        }else {
            return "";
        }
    }

    /**
     * 重构便于简化注入查询的sql语句
     *
     * @param id            查询语句的id
     * @param sqlSource     sql语句
     * @param parameterType 参数类型
     * @param resultType    结果类型
     * @return void
     * @author 陈金波
     * @date 2018年1月4日 上午9:37:04
     */
    private MappedStatement addSelectMappedStatement(String id, SqlSource sqlSource, Class<?> parameterType, Class<?> resultType) {
        return this.addMappedStatement(id, sqlSource, SqlCommandType.SELECT, parameterType, resultType, new NoKeyGenerator(),
                null, null);
    }

    /**
     * 注入sql语句
     *
     * @param id             查询语句的id
     * @param sqlSource      sql语句
     * @param sqlCommandType sql语句的类型，分别有增删改查
     * @param parameterType  参数类型
     * @param resultType     结果类型
     * @param keyGenerator   主键
     * @param keyProperty    主键java字段
     * @param keyColumn      主键数据库字段
     * @return void
     * @author 陈金波
     * @date 2018年1月4日 上午9:38:20
     */
    private MappedStatement addMappedStatement(String id, SqlSource sqlSource, SqlCommandType sqlCommandType,
                                               Class<?> parameterType, Class<?> resultType, KeyGenerator keyGenerator, String keyProperty,
                                               String keyColumn) {
        String statementName = this.type.getName() + "." + id;
        if (configuration.hasStatement(statementName)) {
            logger.warn("{},已通过xml或SqlProvider加载了，忽略该sql的注入", statementName);
            return null;
        }
        return builderAssistant.addMappedStatement(id, sqlSource, StatementType.PREPARED, sqlCommandType, null, null, null,
                parameterType, null, resultType, null, false, true, false, keyGenerator, keyProperty, keyColumn,
                configuration.getDatabaseId(), builderAssistant.getLanguageDriver(null), null);
    }

}
